use super::cast::NumCast;
use super::identities::{One, Zero};

use std::num::Wrapping;
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};

pub trait Num: PartialEq + Zero + One + NumOps {
  type FromStrRadixErr;

  fn from_str_radix(
    str: &str,
    radix: u32,
  ) -> Result<Self, Self::FromStrRadixErr>;
}

pub trait NumOps<Rhs = Self, Output = Self>:
  Add<Rhs, Output = Output>
  + Sub<Rhs, Output = Output>
  + Mul<Rhs, Output = Output>
  + Div<Rhs, Output = Output>
  + Rem<Rhs, Output = Output>
{
}

impl<T, Rhs, Output> NumOps<Rhs, Output> for T where
  T: Add<Rhs, Output = Output>
    + Sub<Rhs, Output = Output>
    + Mul<Rhs, Output = Output>
    + Div<Rhs, Output = Output>
    + Rem<Rhs, Output = Output>
{
}

macro_rules! int_trait_impl {
    ($name:ident for $($t:ty)*) => ($(
        impl $name for $t {
            type FromStrRadixErr = ::std::num::ParseIntError;
            #[inline]
            fn from_str_radix(s: &str, radix: u32)
                              -> Result<Self, ::std::num::ParseIntError>
            {
                <$t>::from_str_radix(s, radix)
            }
        }
    )*)
}

int_trait_impl!(Num for usize u8 u16 u32 u64 isize i8 i16 i32 i64);
int_trait_impl!(Num for u128 i128);

impl<T: Num> Num for Wrapping<T>
where
  Wrapping<T>: Add<Output = Wrapping<T>>
    + Sub<Output = Wrapping<T>>
    + Mul<Output = Wrapping<T>>
    + Div<Output = Wrapping<T>>
    + Rem<Output = Wrapping<T>>,
{
  type FromStrRadixErr = T::FromStrRadixErr;
  fn from_str_radix(
    str: &str,
    radix: u32,
  ) -> Result<Self, Self::FromStrRadixErr> {
    T::from_str_radix(str, radix).map(Wrapping)
  }
}

pub trait Float:
  Num + Copy + NumCast + PartialOrd + Neg<Output = Self>
{
  fn powi(self, n: i32) -> Self;
}

impl Float for f32 {
  fn powi(self, n: i32) -> Self {
    f32::powi(self, n)
  }
}

impl Float for f64 {
  fn powi(self, n: i32) -> Self {
    f64::powi(self, n)
  }
}

#[derive(Debug)]
pub enum FloatErrorKind {
  Empty,
  Invalid,
}

#[derive(Debug)]
pub struct ParseFloatError {
  pub kind: FloatErrorKind,
}

impl std::fmt::Display for ParseFloatError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let description = match self.kind {
      FloatErrorKind::Empty => "can't parse float from empty string",
      FloatErrorKind::Invalid => "invalid float literal",
    };
    description.fmt(f)
  }
}

fn str_to_ascii_lower_eq_str(a: &str, b: &str) -> bool {
  a.len() == b.len()
    && a.bytes().zip(b.bytes()).all(|(a, b)| {
      let a_to_ascii_lower = a | (((b'A' <= a && a <= b'Z') as u8) << 5);
      a_to_ascii_lower == b
    })
}

macro_rules! float_trait_impl {
    ($name:ident for $($t:ident)*) => ($(
        impl $name for $t {
            type FromStrRadixErr = ParseFloatError;

            fn from_str_radix(src: &str, radix: u32)
                              -> Result<Self, Self::FromStrRadixErr>
            {
                use self::FloatErrorKind::*;
                use self::ParseFloatError as PFE;

                // Special case radix 10 to use more accurate standard library implementation
                if radix == 10 {
                    return src.parse().map_err(|_| PFE {
                        kind: if src.is_empty() { Empty } else { Invalid },
                    });
                }

                // Special values
                if str_to_ascii_lower_eq_str(src, "inf")
                    || str_to_ascii_lower_eq_str(src, "infinity")
                {
                    return Ok(core::$t::INFINITY);
                } else if str_to_ascii_lower_eq_str(src, "-inf")
                    || str_to_ascii_lower_eq_str(src, "-infinity")
                {
                    return Ok(core::$t::NEG_INFINITY);
                } else if str_to_ascii_lower_eq_str(src, "nan") {
                    return Ok(core::$t::NAN);
                } else if str_to_ascii_lower_eq_str(src, "-nan") {
                    return Ok(-core::$t::NAN);
                }

                fn slice_shift_char(src: &str) -> Option<(char, &str)> {
                    let mut chars = src.chars();
                    if let Some(ch) = chars.next() {
                        Some((ch, chars.as_str()))
                    } else {
                        None
                    }
                }

                let (is_positive, src) =  match slice_shift_char(src) {
                    None             => return Err(PFE { kind: Empty }),
                    Some(('-', ""))  => return Err(PFE { kind: Empty }),
                    Some(('-', src)) => (false, src),
                    Some((_, _))     => (true,  src),
                };

                // The significand to accumulate
                let mut sig = if is_positive { 0.0 } else { -0.0 };
                // Necessary to detect overflow
                let mut prev_sig = sig;
                let mut cs = src.chars().enumerate();
                // Exponent prefix and exponent index offset
                let mut exp_info = None::<(char, usize)>;

                // Parse the integer part of the significand
                for (i, c) in cs.by_ref() {
                    match c.to_digit(radix) {
                        Some(digit) => {
                            // shift significand one digit left
                            sig = sig * (radix as $t);

                            // add/subtract current digit depending on sign
                            if is_positive {
                                sig = sig + ((digit as isize) as $t);
                            } else {
                                sig = sig - ((digit as isize) as $t);
                            }

                            // Detect overflow by comparing to last value, except
                            // if we've not seen any non-zero digits.
                            if prev_sig != 0.0 {
                                if is_positive && sig <= prev_sig
                                    { return Ok(core::$t::INFINITY); }
                                if !is_positive && sig >= prev_sig
                                    { return Ok(core::$t::NEG_INFINITY); }

                                // Detect overflow by reversing the shift-and-add process
                                if is_positive && (prev_sig != (sig - digit as $t) / radix as $t)
                                    { return Ok(core::$t::INFINITY); }
                                if !is_positive && (prev_sig != (sig + digit as $t) / radix as $t)
                                    { return Ok(core::$t::NEG_INFINITY); }
                            }
                            prev_sig = sig;
                        },
                        None => match c {
                            'e' | 'E' | 'p' | 'P' => {
                                exp_info = Some((c, i + 1));
                                break;  // start of exponent
                            },
                            '.' => {
                                break;  // start of fractional part
                            },
                            _ => {
                                return Err(PFE { kind: Invalid });
                            },
                        },
                    }
                }

                // If we are not yet at the exponent parse the fractional
                // part of the significand
                if exp_info.is_none() {
                    let mut power = 1.0;
                    for (i, c) in cs.by_ref() {
                        match c.to_digit(radix) {
                            Some(digit) => {
                                // Decrease power one order of magnitude
                                power = power / (radix as $t);
                                // add/subtract current digit depending on sign
                                sig = if is_positive {
                                    sig + (digit as $t) * power
                                } else {
                                    sig - (digit as $t) * power
                                };
                                // Detect overflow by comparing to last value
                                if is_positive && sig < prev_sig
                                    { return Ok(core::$t::INFINITY); }
                                if !is_positive && sig > prev_sig
                                    { return Ok(core::$t::NEG_INFINITY); }
                                prev_sig = sig;
                            },
                            None => match c {
                                'e' | 'E' | 'p' | 'P' => {
                                    exp_info = Some((c, i + 1));
                                    break; // start of exponent
                                },
                                _ => {
                                    return Err(PFE { kind: Invalid });
                                },
                            },
                        }
                    }
                }

                // Parse and calculate the exponent
                let exp = match exp_info {
                    Some((c, offset)) => {
                        let base = match c {
                            'E' | 'e' if radix == 10 => 10.0,
                            'P' | 'p' if radix == 16 => 2.0,
                            _ => return Err(PFE { kind: Invalid }),
                        };

                        // Parse the exponent as decimal integer
                        let src = &src[offset..];
                        let (is_positive, exp) = match slice_shift_char(src) {
                            Some(('-', src)) => (false, src.parse::<usize>()),
                            Some(('+', src)) => (true,  src.parse::<usize>()),
                            Some((_, _))     => (true,  src.parse::<usize>()),
                            None             => return Err(PFE { kind: Invalid }),
                        };

                        fn pow(base: $t, exp: usize) -> $t {
                            Float::powi(base, exp as i32)
                        }
                        // otherwise uses the generic `pow` from the root

                        match (is_positive, exp) {
                            (true,  Ok(exp)) => pow(base, exp),
                            (false, Ok(exp)) => 1.0 / pow(base, exp),
                            (_, Err(_))      => return Err(PFE { kind: Invalid }),
                        }
                    },
                    None => 1.0, // no exponent
                };

                Ok(sig * exp)
            }
        }
    )*)
}

float_trait_impl!(Num for f32 f64);
